package org.icop.frame.boost;

import java.util.HashSet;
import java.util.Set;

import org.icop.model.MultiSet;
import org.icop.model.ValuedCorrespondence;
import org.icop.utils.RPSTCache;
import org.jbpt.algo.tree.rpst.IRPST;
import org.jbpt.algo.tree.rpst.IRPSTNode;
import org.jbpt.algo.tree.tctree.TCType;
import org.jbpt.graph.abs.IDirectedEdge;
import org.jbpt.graph.abs.IDirectedGraph;
import org.jbpt.hypergraph.abs.IVertex;

/**
 * We boost matches based on the depth of the node sets in the
 * respective RPST. That is, for both node sets of the match,
 * we determine the lowest common ancestor (LCA) and the max depth 
 * of tree.
 * 
 * First, we determine the average of the ratios of LCA depth and max 
 * tree depth for both node sets. If this is above a threshold we boost
 * the match.
 * 
 * Second, we determine the difference of the two ratios (LCA depth and 
 * max tree depth). If this differences is less than a threshold, we boost
 * the match as well.
 * 
 * @author matthias.weidlich
 *
 */
public class MatchRPSTDepthBooster<G extends IDirectedGraph<E,V>, E extends IDirectedEdge<V>, V extends IVertex> implements Booster<G, E, V> {

	public static double depthBoostFactor;
	public static double boostWithDepthRatioAbove;
	
	public static double depthDifferenceBoostFactor;
	public static double boostWithDepthRatioDifferenceBelow;
	
	public MatchRPSTDepthBooster() {
		this(0.5,0.5,0.3,0.1);
	}
	
	public MatchRPSTDepthBooster(
			double depthBoostFactor,
			double boostWithDepthRatioAbove,
			double depthDifferenceBoostFactor,
			double boostWithDepthRatioDifferenceBelow) {
		MatchRPSTDepthBooster.depthBoostFactor = depthBoostFactor;
		MatchRPSTDepthBooster.depthDifferenceBoostFactor = depthDifferenceBoostFactor;
		MatchRPSTDepthBooster.boostWithDepthRatioAbove = boostWithDepthRatioAbove;
		MatchRPSTDepthBooster.boostWithDepthRatioDifferenceBelow = boostWithDepthRatioDifferenceBelow;
	}
	
	@Override
	public MultiSet<ValuedCorrespondence<V>> boost(G sg1, G sg2,
			MultiSet<ValuedCorrespondence<V>> mapping) {

		@SuppressWarnings("unchecked")
		IRPST<E, V> rpsTree1 = (IRPST<E, V>) RPSTCache.getInstance().getRPSTForGraph((IDirectedGraph<IDirectedEdge<IVertex>, IVertex>) sg1);
		@SuppressWarnings("unchecked")
		IRPST<E, V> rpsTree2 = (IRPST<E, V>) RPSTCache.getInstance().getRPSTForGraph((IDirectedGraph<IDirectedEdge<IVertex>, IVertex>) sg2);

		for (ValuedCorrespondence<V> sets : mapping) {
			
			float depthRatio1 = getDepthRatio(rpsTree1,sets.getV1s());
			float depthRatio2 = getDepthRatio(rpsTree2,sets.getV2s());
			
			boostBasedOnDepthRatio(sets, (depthRatio1 + depthRatio2)/2);
			boostBasedOnDepthRatioDifference(sets,Math.abs(depthRatio1 - depthRatio2));
		}
		return mapping;
	}
	
	protected float getDepthRatio(IRPST<E, V> tree, Set<V> nodes) {
		/*
		 * Determine LCA
		 */
		Set<IRPSTNode<E,V>> relevantNodes = new HashSet<>();
		for (IRPSTNode<E, V> n : tree.getRPSTNodes(TCType.TRIVIAL)) {
			if (n.getEntry() != null)
				if (nodes.contains(n.getEntry()))
					relevantNodes.add(n);
		}
		
		IRPSTNode<E, V> lca = tree.getLCA(relevantNodes);
		
		float maxDepth = 0;
		for (IRPSTNode<E, V> treeNode : tree.getRPSTNodes(TCType.TRIVIAL)) {
			maxDepth = Math.max(maxDepth, tree.getDownwardPath(tree.getRoot(), treeNode).size());
		}
		float lcaDepth = tree.getDownwardPath(tree.getRoot(), lca).size();
		
		/*
		 * Avoid a ratio of zero. 
		 */
		maxDepth++;
		lcaDepth++;
		
		return lcaDepth / maxDepth;
	}
	
	protected void boostBasedOnDepthRatio(ValuedCorrespondence<V> sets, float depthRatio) {
		if (depthRatio < boostWithDepthRatioAbove)
			return;
		double diff = 1 - sets.getConfidenceValue();
		sets.setConfidenceValue(sets.getConfidenceValue() + diff * depthBoostFactor * depthRatio);

	}
	
	protected void boostBasedOnDepthRatioDifference(ValuedCorrespondence<V> sets, float depthDifference) {
		if (depthDifference > boostWithDepthRatioDifferenceBelow)
			return;
		double diff = 1 - sets.getConfidenceValue();
		sets.setConfidenceValue(sets.getConfidenceValue() + diff * depthDifferenceBoostFactor * (1 - depthDifference));
	}
}
